<?php


namespace app\api\helpers;


use app\api\model\v1\Admins;
use app\api\model\v1\AuthGroup;
use app\api\model\v1\AuthGroupAccess;
use app\api\model\v1\AuthRule;
use think\facade\Config;
use think\facade\Cookie;
use think\facade\Db;
use think\facade\Session;
use think\Request;

class Auth
{

    /**
     * @var object 对象实例
     */
    protected static $instance;
    protected $rules = [];
    protected $logined = false; //登录状态

    /**
     * 当前请求实例
     * @var Request
     */
    protected $request;
    //默认配置
    protected $config = [
        'auth_on'           => 1, // 权限开关
        'auth_type'         => 1, // 认证方式，1为实时认证；2为登录认证。
        'auth_group'        => 'auth_group', // 用户组数据表名
        'auth_group_access' => 'auth_group_access', // 用户-用户组关系表
        'auth_rule'         => 'auth_rule', // 权限规则表
    ];
    /**
     * @param $name  //需要验证的规则列表,支持逗号分隔的权限规则或索引数组
     * @param $uid      //认证用户的id
     * @param string $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
     * @param string $mode 执行验证的模式,可分为url,normal
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:14
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function check($name, $uid, $relation = 'or', $mode = 'url'){
        if (!$this->config['auth_on']) {
            return true;
        }
        // 获取用户需要验证的所有有效规则列表
        $rulelist = $this->getRuleList($uid);
        if (in_array('*', $rulelist)) {
            return true;
        }

        if (is_string($name)) {
            $name = strtolower($name);
            if (strpos($name, ',') !== false) {
                $name = explode(',', $name);
            } else {
                $name = [$name];
            }
        }
        $list = []; //保存验证通过的规则名
        if ('url' == $mode) {
            $REQUEST = unserialize(strtolower(serialize($this->request->param())));
        }

        foreach ($rulelist as $rule) {
            $query = preg_replace('/^.+\?/U', '', $rule);
            if ('url' == $mode && $query != $rule) {
                parse_str($query, $param); //解析规则中的param
                $intersect = array_intersect_assoc($REQUEST, $param);
                $rule = preg_replace('/\?.*$/U', '', $rule);
                if (in_array($rule, $name) && $intersect == $param) {
                    //如果节点相符且url参数满足
                    $list[] = $rule;
                }
            } else {
                if (in_array($rule, $name)) {
                    $list[] = $rule;
                }
            }
        }
        if ('or' == $relation && !empty($list)) {
            return true;
        }
        $diff = array_diff($name, $list);
        if ('and' == $relation && empty($diff)) {
            return true;
        }
        return false;
    }

    /**
     * @param $uid
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:14
     * @return array|mixed
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     *  获得权限规则列表
     */
    public function getRuleList($uid){

        static $_rulelist = []; //保存用户验证通过的权限列表
        if (isset($_rulelist[$uid])) {
            return $_rulelist[$uid];
        }

        if (2 == $this->config['auth_type'] && Session::has('_rule_list_' . $uid)) {
            return Session::get('_rule_list_' . $uid);
        }
        // 读取用户规则节点
        $ids = $this->getRuleIds($uid);

        if (empty($ids)) {
            $_rulelist[$uid] = [];
            return [];
        }

        // 筛选条件
        $where = [
            'is_open' => 1
        ];
        if (!in_array('*', $ids)) {
            $where['id'] = ['in', $ids];
        }
        //读取用户组所有权限规则
        $this->rules = Db::name($this->config['auth_rule'])->where($where)->field('id,pid,condition,icon,name,title,is_menu')->select();

        //循环规则，判断结果。
        $rulelist = []; //
        if (in_array('*', $ids)) {
            $rulelist[] = "*";
        }
        foreach ($this->rules as $rule) {
            //超级管理员无需验证condition
            if (!empty($rule['condition']) && !in_array('*', $ids)) {
                //根据condition进行验证
                $user = $this->getUserInfo($uid); //获取用户信息,一维数组
                $nums = 0;
                $condition = str_replace(['&&', '||'], "\r\n", $rule['condition']);
                $condition = preg_replace('/\{(\w*?)\}/', '\\1', $condition);
                $conditionArr = explode("\r\n", $condition);
                foreach ($conditionArr as $index => $item) {
                    preg_match("/^(\w+)\s?([\>\<\=]+)\s?(.*)$/", trim($item), $matches);
                    if ($matches && isset($user[$matches[1]]) && version_compare($user[$matches[1]], $matches[3], $matches[2])) {
                        $nums++;
                    }
                }
                if ($conditionArr && ((stripos($rule['condition'], "||") !== false && $nums > 0) || count($conditionArr) == $nums)) {
                    $rulelist[$rule['id']] = strtolower($rule['name']);
                }
            } else {
                //只要存在就记录
                $rulelist[$rule['id']] = strtolower($rule['name']);
            }
        }
        $_rulelist[$uid] = $rulelist;
        //登录验证则需要保存规则列表
        if (2 == $this->config['auth_type']) {
            //规则列表结果保存到session
            Session::set('_rule_list_' . $uid, $rulelist);
        }
        return array_unique($rulelist);
    }

    /**
     * @param $uid  //用户id
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:13
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 保存用户所属用户组设置的所有权限规则id
     */
    public function getRuleIds($uid){
        //读取用户所属用户组
        $groups = $this->getGroups($uid);
        $ids = []; //保存用户所属用户组设置的所有权限规则id
        foreach ($groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }


        $ids = array_unique($ids);

        return $ids;
    }

    /**
     * @param $uid  //用户id
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:12
     *
     * @return array|mixed|\think\Collection|Db[]  用户所属的用户组 array(
     *                  array('uid'=>'用户id','group_id'=>'用户组id','name'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
     *                  ...)
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 根据用户id获取用户组,返回值为数组
     */
    public function getGroups($uid){
        static $groups = [];
        if (isset($groups[$uid])) {
            return $groups[$uid];
        }

        // 执行查询
        $user_groups = Db::name($this->config['auth_group_access'])
            ->alias('aga')
            ->join($this->config['auth_group'] . ' ag', 'aga.group_id = ag.id', 'LEFT')
            ->field('aga.uid,aga.group_id,ag.id,ag.pid,ag.name,ag.rules')
            ->where("aga.uid='{$uid}' and ag.status=1")
            ->select();

        $groups[$uid] = $user_groups ?: [];

        return $groups[$uid];
    }

    /**
     * @param $uid
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:15
     * @return array|Db|\think\Model|null
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 获得用户资料
     */
    public function getUserInfo($uid){
        static $user_info = [];

        $user = Db::name($this->config['auth_user']);
        // 获取用户表主键
        $_pk = is_string($user->getPk()) ? $user->getPk() : 'uid';
        if (!isset($user_info[$uid])) {
            $user_info[$uid] = $user->where($_pk, $uid)->find();
        }

        return $user_info[$uid];
    }

    /**
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:32
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 监测是否登录
     */
    public function isLogin(){
        if ($this->logined) {
            return true;
        }
        $admin = Session::get('admin');
        if (!$admin) {
            return false;
        }
        //判断是否同一时间同一账号只能在一个地方登录
        if (Config::get('app.system.login_unique')) {
            $my = Admins::find($admin['id']);
            if (!$my || $my['token'] != $admin['token']) {
                $this->logined = false; //重置登录状态
                Session::delete("admin");
                Cookie::delete("keep_login");
                return false;
            }
        }
        //判断管理员IP是否变动
        if (Config::get('app.system.login_ip_check')) {
            if (!isset($admin['login_ip']) || $admin['login_ip'] != request()->ip()) {
                $this->logout($admin['id']);
                return false;
            }
        }
        $this->logined = true;
        return true;

    }

    /**
     * @param $uid
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 15:32
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 退出
     */
    public function logout($uid){
        $admin = Admins::find(intval($uid));
        if ($admin) {
            $admin->token = '';
            $admin->save();
        }
        $this->logined = false; //重置登录状态
        Session::delete("admin");
        Cookie::delete("keep_login");
        return true;
    }

    /**
     * @param $keeptime
     * @param $uid
     * @param $token
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:06
     * @return bool
     * 刷新保持登录的Cookie
     */
    protected function keep_login($keeptime=0,$uid,$token)
    {
        if ($keeptime) {
            $expiretime = time() + $keeptime;
            $key = md5(md5($uid) . md5($keeptime) . md5($expiretime) .$token . Config::get('app.system.key'));
            $data = [$uid, $keeptime, $expiretime, $key];
            Cookie::set('keep_login', implode('|', $data), 86400 * 7);
            return true;
        }
        return false;
    }

    /**
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:09
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 自动登录
     */
    public function autologin()
    {
        $keeplogin = Cookie::get('keep_login');
        if (!$keeplogin) {
            return false;
        }
        list($id, $keeptime, $expiretime, $key) = explode('|', $keeplogin);
        if ($id && $keeptime && $expiretime && $key && $expiretime > time()) {
            $admin = Admins::find($id);
            if (!$admin || !$admin->token) {
                return false;
            }
            //token有变更
            if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $admin->token . Config::get('app.system.key'))) {
                return false;
            }
            $ip = $this->request->ip();
            //IP有变动
            if ($admin->login_ip != $ip) {
                return false;
            }
            Session::set("admin", $admin->toArray());
            //刷新自动登录的时效
            $this->keep_login($keeptime,$admin->id,$admin->token);
            return true;
        } else {
            return false;
        }
    }

    /**
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:10
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 是否是超级管理员
     */
    public function isSuperAdmin($uid)
    {
        return in_array('*', $this->getRuleIds($uid)) ? true : false;
    }

    /**
     * @param null $uid
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:11
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 获取管理员所属于的分组ID
     */
    public function getGroupIds($uid = null)
    {
        $groups = $this->getGroups($uid);
        $groupIds = [];
        foreach ($groups as $K => $v) {
            $groupIds[] = (int)$v['group_id'];
        }
        return $groupIds;
    }


    /**
     * @param false $withself
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:26
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 取出当前管理员所拥有权限的分组
     */
    public function getChildrenGroupIds($withself = false,$uid)
    {
        //取出当前管理员所有的分组
        $groups = $this->getGroups($uid);
        $groupIds = [];
        foreach ($groups as $k => $v) {
            $groupIds[] = $v['id'];
        }
        $originGroupIds = $groupIds;
        foreach ($groups as $k => $v) {
            if (in_array($v['pid'], $originGroupIds)) {
                $groupIds = array_diff($groupIds, [$v['id']]);
                unset($groups[$k]);
            }
        }
        // 取出所有分组
        $groupList = AuthGroup::where(['status' => 1])->select();
        $objList = [];
        foreach ($groups as $k => $v) {
            if ($v['rules'] === '*') {
                $objList = $groupList;
                break;
            }
            // 取出包含自己的所有子节点
            $childrenList = Tree::instance()->init($groupList, 'pid')->getChildren($v['id'], true);
            $obj = Tree::instance()->init($childrenList, 'pid')->getTreeArray($v['pid']);
            $objList = array_merge($objList, Tree::instance()->getTreeList($obj));
        }
        $childrenGroupIds = [];
        foreach ($objList as $k => $v) {
            $childrenGroupIds[] = $v['id'];
        }
        if (!$withself) {
            $childrenGroupIds = array_diff($childrenGroupIds, $groupIds);
        }
        return $childrenGroupIds;
    }

    /**
     * @param false $withself
     * @User: 刘海龙
     * @Date: 2021/9/16
     * @Time: 16:26
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * 取出当前管理员所拥有权限的管理员
     */
    public function getChildrenAdminIds($withself = false,$uid)
    {
        $childrenAdminIds = [];
        if (!$this->isSuperAdmin($uid)) {
            $groupIds = $this->getChildrenGroupIds(false,$uid);
            $authGroupList = AuthGroupAccess::
            field('uid,group_id')
                ->where('group_id', 'in', $groupIds)
                ->select();
            foreach ($authGroupList as $k => $v) {
                $childrenAdminIds[] = $v['uid'];
            }
        } else {
            //超级管理员拥有所有人的权限
            $childrenAdminIds = Admins::column('id');
        }
        if ($withself) {
            if (!in_array($uid, $childrenAdminIds)) {
                $childrenAdminIds[] = $uid;
            }
        } else {
            $childrenAdminIds = array_diff($childrenAdminIds, [$uid]);
        }
        return $childrenAdminIds;
    }

    public function getBreadCrumb($path = '')
    {
        $titleArr = [];
        $menuArr = [];
        $urlArr = explode('/', $path);
        foreach ($urlArr as $index => $item) {
            $pathArr[implode('/', array_slice($urlArr, 3, $index+3))] = $index;
        }
        if(!$this->rules){
            $this->getRuleList(1);
        }
        foreach ($this->rules as $rule) {
            if (isset($pathArr[$rule['name']])) {

                $menuArr[$pathArr[$rule['name']]] = $rule;
            }

        }
        ksort($menuArr);
        $this->breadcrumb = $menuArr;
        return $this->breadcrumb;
    }
}
